/* packet_dialog.cpp
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "packet_dialog.h"
#include <ui_packet_dialog.h>
#include "apputils.h"
#include "lua_dissector.h"

#include "ftypes.h"

//#include "frame_tvbuff.h"

#include <utf8_entities.h>

#include "byte_view_tab.h"
#include "proto_tree.h"

#include <field_information.h>
#include <QTreeWidgetItemIterator>

typedef enum {
    layout_vertical,
    layout_horizontal
} splitter_layout_e;

Q_DECLARE_METATYPE(splitter_layout_e)

// To do:
// - Copy over experimental packet editing code.
// - Fix ElidedText width.

PacketDialog::PacketDialog(QWidget *parent, const QByteArray &fdata) :
    QDialog(parent),
    ui(new Ui::PacketDialog),
    proto_tree_(NULL),
    byte_view_tab_(NULL)
{
    ui->setupUi(this);

    setWindowTitle(tr("Packet"));

    ui->hintLabel->setSmallText();
    ui->prefsLayout->insertSpacing(1, 20);
    ui->prefsLayout->addStretch();

    ui->layoutComboBox->addItem(QString("Vertical (Stacked)"), layout_vertical);
    ui->layoutComboBox->addItem(QString("Horizontal (Side-by-Side)"), layout_horizontal);
    ui->layoutComboBox->setCurrentIndex(0);
    Qt::Orientation pref_orientation = Qt::Vertical;
//    switch(prefs.gui_packet_dialog_layout) {
//    case(layout_vertical):
//        pref_orientation = Qt::Vertical;
//        break;
//    case(layout_horizontal):
//        pref_orientation = Qt::Horizontal;
//        break;
//    }

    if (ui->packetSplitter->orientation() != pref_orientation) {
        ui->packetSplitter->setOrientation(pref_orientation);
        // If the orientation is different than the restore one,
        // reset the sizes to 50-50.
        QList<int> sizes = ui->packetSplitter->sizes();
        int totalsize = sizes.at(0) + sizes.at(1);
        sizes[0] = totalsize / 2;
        sizes[1] = totalsize / 2;
        ui->packetSplitter->setSizes(sizes);
    }

    /* Handle preference value correctly */
    Qt::CheckState state = Qt::Checked;

    ui->chkShowByteView->setCheckState(state);
    ui->layoutComboBox->setEnabled(state);

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    connect(ui->layoutComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PacketDialog::layoutChanged);
#else
    connect(ui->layoutComboBox, &QComboBox::currentIndexChanged, this, &PacketDialog::layoutChanged, Qt::AutoConnection);
#endif
    connect(ui->chkShowByteView, &QCheckBox::stateChanged, this, &PacketDialog::viewVisibilityStateChanged);


    tvbuff_t *tvb = NULL;
    tvb = tvb_new_real_data((const uint8_t*)fdata.data(), fdata.length(), fdata.length());
    tvb_set_free_cb(tvb, free);

    /* proto tree, visible. We need a proto tree if there are custom columns */
    ldt_ = lua_dissect_new();

    lua_dissect_run(ldt_, tvb);

    // Retrieve the effective font and apply it.
    QFont mono_font_ = AppUtils::monoFont();

    proto_tree_ = new ProtoTree(ui->packetSplitter, ldt_);
    proto_tree_->setMonospaceFont(mono_font_);
    proto_tree_->setRootNode(ldt_->tree);

    byte_view_tab_ = new ByteViewTab(ui->packetSplitter, ldt_);
    byte_view_tab_->setMonospaceFont(mono_font_);
    byte_view_tab_->selectedFrameChanged(QList<int>() << 0);


    connect(byte_view_tab_, SIGNAL(fieldSelected(FieldInformation *)),
            proto_tree_, SLOT(selectedFieldChanged(FieldInformation *)));
    connect(proto_tree_, SIGNAL(fieldSelected(FieldInformation *)),
            byte_view_tab_, SLOT(selectedFieldChanged(FieldInformation *)));

    connect(byte_view_tab_, SIGNAL(fieldHighlight(FieldInformation *)),
            this, SLOT(setHintText(FieldInformation *)));
    connect(byte_view_tab_, &ByteViewTab::fieldSelected,
            this, &PacketDialog::setHintTextSelected);
    connect(proto_tree_, &ProtoTree::fieldSelected,
            this, &PacketDialog::setHintTextSelected);
}

PacketDialog::~PacketDialog()
{
    delete ui;
    lua_dissect_free(ldt_);
}

void PacketDialog::setHintText(FieldInformation * finfo)
{
    QString hint;

     if (finfo)
     {
         FieldInformation::Position pos = finfo->position();
         QString field_str;

         if (pos.length < 2) {
             hint = QString(tr("Byte %1")).arg(pos.start);
         } else {
             hint = QString(tr("Bytes %1-%2")).arg(pos.start).arg(pos.start + pos.length - 1);
         }
         hint += QString(": %1 (%2)")
                 .arg(finfo->headerInfo().name)
                 .arg(finfo->headerInfo().abbreviation);
     }
     else {
         hint = col_info_;
     }
     ui->hintLabel->setText(hint);
}

void PacketDialog::setHintTextSelected(FieldInformation* finfo)
{
    QString hint;

    if (finfo)
    {
        FieldInformation::HeaderInfo hInfo = finfo->headerInfo();

        if (hInfo.isValid)
        {
            if (hInfo.description.length() > 0) {
                hint.append(hInfo.description);
            }
            else {
                hint.append(hInfo.name);
            }
        }

        if (!hint.isEmpty()) {
            int finfo_length;
            if (hInfo.isValid)
                hint.append(" (" + hInfo.abbreviation + ")");

            finfo_length = finfo->position().length + finfo->appendix().length;
            if (finfo_length > 0) {
                int finfo_bits = FI_GET_BITS_SIZE(finfo->fieldInfo());
                if (finfo_bits % 8 == 0) {
                    hint.append(", " + tr("%Ln byte(s)", "", finfo_length));
                } else {
                    hint.append(", " + tr("%Ln bit(s)", "", finfo_bits));
                }
            }
        }
    }
    else {
        hint = col_info_;
    }
    ui->hintLabel->setText(hint);
}

void PacketDialog::viewVisibilityStateChanged(int state)
{
    byte_view_tab_->setVisible(state == Qt::Checked);
    ui->layoutComboBox->setEnabled(state == Qt::Checked);
}

void PacketDialog::layoutChanged(int index)
{
    splitter_layout_e layout = ui->layoutComboBox->currentData().value<splitter_layout_e>();

    switch(layout) {
    case(layout_vertical):
        ui->packetSplitter->setOrientation(Qt::Vertical);
        break;
    case(layout_horizontal):
        ui->packetSplitter->setOrientation(Qt::Horizontal);
        break;
    }
}
